﻿using System;
using SoftwareConsulting.BI11.NumericalMethods.DhbFunctionEvaluation;
using SoftwareConsulting.BI11.NumericalMethods.Curves;
using SoftwareConsulting.BI11.NumericalMethods.Statistics;
using SoftwareConsulting.BI11.Studio.InternalServices;

namespace SoftwareConsulting.BI11.NumericalMethods.Statistics
{
    /// <summary>
    /// Класс распределения Вейбулла
    /// </summary>
    public sealed class WeibullDistribution : ProbabilityDensityFunction
    {    	 
        private double _alphaDistributionShape;//форма рапределения	    
        private double _betaDistributionScale;//масштаб распределения	    
        private double _norm;//норма (для ускорения)

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="alphaDistributionShape">форма рапределения</param>
        /// <param name="betaDistributionScale">масштаб распределения</param>
        public WeibullDistribution(double alphaDistributionShape, double betaDistributionScale)
        {
            if (alphaDistributionShape <= 0)
                throw new ArgumentOutOfRangeException("alphaDistributionShape");
            if (betaDistributionScale <= 0)
                throw new ArgumentOutOfRangeException("betaDistributionScale");
            SetParameters(alphaDistributionShape, betaDistributionScale);
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="histogram">Гистограмма, из которой вытягиваются параметры по наилучшим предположениям</param>
        public WeibullDistribution(Histogram histogram)
        {
            if (histogram.Minimum < 0)
                throw new ArgumentOutOfRangeException();
            double average = histogram.Average;
            if (average <= 0)
                throw new ArgumentOutOfRangeException();
            double xMin = (histogram.Minimum + average) * 0.5;
            double accMin = Math.Log(-Math.Log(1 - histogram.GetSumUpTo(xMin) / histogram.TotalCount));
            double xMax = (histogram.Maximum + average) * 0.5;
            double accMax = Math.Log(-Math.Log(1 - histogram.GetSumUpTo(xMax) / histogram.TotalCount));
            double delta = accMax - accMin;
            xMin = Math.Log(xMin);
            xMax = Math.Log(xMax);
            SetParameters(delta / (xMax - xMin), Math.Exp((accMax * xMin - accMin * xMax) / delta));
        }

        /// <summary>
        /// Возвращает среднее распределения
        /// </summary>
        public override double Average
        {
            get 
            { 
                return GammaFunction.Gamma(1 / _alphaDistributionShape) * _betaDistributionScale / _alphaDistributionShape; 
            }
            set
            {
                Assert.Fail(Assert.Id.Assert00294);//для этого распределения не поддерживается
            }
        }

        /// <summary>
        /// Задает новые параметры распределения
        /// </summary>
        /// <param name="alphaDistributionShape">форма распределения</param>
        /// <param name="betaDistributionScale">масштаб распределения</param>
        public void SetParameters(double alphaDistributionShape, double betaDistributionScale)
        {
            _alphaDistributionShape = alphaDistributionShape;
            _betaDistributionScale = betaDistributionScale;
            _norm = _alphaDistributionShape / Math.Pow(_betaDistributionScale, _alphaDistributionShape);
        }

        /// <summary>
        /// Возвращает значение функции плотности распределения
        /// </summary>
        /// <param name="x"></param>
        /// <returns>вероятность, нахождения случайной переменной меньше или равной, чем X</returns>
        public override double GetDistributionValue(double x)
        {
            return 1.0 - Math.Exp(-Math.Pow(x / _betaDistributionScale, _alphaDistributionShape));
        }

        /// <summary>
        /// Возвращает название распределения
        /// </summary>
        public override string Name
        {
            get 
            { 
                return "Weibull distribution"; 
            }
        }

        /// <summary>
        /// Возвращает/устанавливает параметры распределения
        /// </summary>
        public override double[] Parameters
        {
            get 
            { 
                return new double[] { _alphaDistributionShape, _betaDistributionScale }; 
            }
            set 
            { 
                SetParameters(value[0], value[1]); 
            }
        }

        /// <summary>
        /// Возвращает значение, для которого функция распределения равна указанному значению X
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        protected override double GetInversedValue(double x)
        {
            return Math.Pow(-Math.Log(1 - x), 1.0 / _alphaDistributionShape) * _betaDistributionScale;
        }

        /// <summary>
        /// Возвращает как строку
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format("{0} ({1:####0.00000},{2:####0.00000})", Name, _alphaDistributionShape, _betaDistributionScale);
        }

        /// @return double probability density function
        /// @param x double random variable
        public override double GetValue(double x)
        {
            return _norm * Math.Pow(x, _alphaDistributionShape - 1) * Math.Exp(-Math.Pow(x / _betaDistributionScale, _alphaDistributionShape));
        }

        /// <summary>
        /// Возвращает дисперсию распределения
        /// </summary>
        public override double Variance
        {
            get
            {
                double s = GammaFunction.Gamma(1 / _alphaDistributionShape);
                return _betaDistributionScale * _betaDistributionScale * (2 * GammaFunction.Gamma(2 / _alphaDistributionShape)
                                                        - s * s / _alphaDistributionShape) / _alphaDistributionShape;
            }
        }
    }
}
